package cn.song.controller;

import cn.hutool.json.JSONUtil;
import cn.song.model.R;
import cn.song.util.ExcelReadImageUtil;
import cn.song.util.FilePath;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.listener.PageReadListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

/**
 * 图片处理控制器
 *
 * <p>提供图片相关的REST API接口，主要功能包括：</p>
 * <ul>
 *   <li>从Excel文件中提取图片 - 使用Apache POI解析Excel文件并提取其中的图片资源</li>
 *   <li>图片压缩处理 - 支持多种格式的图片压缩，可指定目标大小</li>
 *   <li>图片模板下载 - 生成包含图片的Excel模板文件</li>
 *   <li>图片文件访问 - 提供Web端图片资源访问接口</li>
 *   <li>图片信息查询 - 获取图片文件的详细信息</li>
 * </ul>
 *
 * <p>技术栈：</p>
 * <ul>
 *   <li>EasyExcel 3.3.2 - 阿里巴巴开源的Excel操作库，用于生成包含图片的Excel文件</li>
 *   <li>Apache POI - 用于从Excel文件中提取图片资源</li>
 *   <li>Spring Boot Web - 提供REST API接口</li>
 *   <li>Java NIO - 文件I/O操作</li>
 * </ul>
 *
 * <p>支持的图片格式：JPG、JPEG、PNG、GIF、BMP</p>
 *
 * @author SongRenShuo
 * @version 1.0
 * @since 2025-01-21
 * @see ExcelReadImageUtil
 * @see cn.song.util.FilePath
 */
@Slf4j
@RestController
@RequestMapping("/api/image")
public class ImageController {
    /**
     * 从Excel文件中提取图片资源
     *
     * <p>此方法使用Apache POI技术从上传的Excel文件中提取所有图片资源，
     * 并将提取的图片保存到指定的输出目录中。支持Excel 2007(.xlsx)和Excel 2003(.xls)格式。</p>
     *
     * <p>处理流程：</p>
     * <ol>
     *   <li>Ⅰ. 验证上传文件的有效性</li>
     *   <li>Ⅱ. 创建图片输出目录(output/readImage)</li>
     *   <li>Ⅲ. 调用ExcelReadImageUtil工具类解析Excel并提取图片</li>
     *   <li>Ⅳ. 扫描输出目录获取已提取的图片文件列表</li>
     *   <li>Ⅴ. 构建响应数据并返回提取结果</li>
     * </ol>
     *
     * <p>支持的图片格式：JPG、JPEG、PNG、GIF</p>
     *
     * @param file 包含图片的Excel文件，支持.xlsx和.xls格式
     * @return 包含提取结果的响应对象，包括图片数量、文件路径列表、输出目录等信息
     * @since 1.0
     * @see ExcelReadImageUtil#readImage(InputStream, List)
     */
    @PostMapping("/extract/excel")
    public R<Map<String, Object>> extractImagesFromExcel(@RequestParam("file") MultipartFile file) {
        // 添加性能监控
        long startTime = System.currentTimeMillis();

        try {
            // Ⅰ. 验证上传文件的有效性
            if (file.isEmpty()) {
                return R.failed("请选择Excel文件");
            }

            long fileSize = file.getSize();
            String fileName = file.getOriginalFilename();
            log.info("开始处理Excel图片提取: 文件={}, 大小={} bytes", fileName, fileSize);

            String uuid = UUID.randomUUID().toString();

            // Ⅱ. 创建图片输出目录结构——性能监控点
            long dirStartTime = System.currentTimeMillis();
            String outputDir = "output" + File.separator + "readImage" + File.separator + uuid + File.separator;
            File dir = new File(outputDir);
            if (!dir.exists()) {
                // 递归创建目录结构
                boolean created = dir.mkdirs();
                if (!created) {
                    log.error("无法创建输出目录: {}", outputDir);
                    return R.failed("无法创建输出目录");
                }
                long dirTime = System.currentTimeMillis() - dirStartTime;
                log.debug("目录创建耗时: {} ms - {}", dirTime, outputDir);
            }
            List<ExcelReadImageUtil.DemoExcelInput> demoExcelDataList = new ArrayList<>();

            EasyExcel.read(file.getInputStream(), ExcelReadImageUtil.DemoExcelInput.class, new PageReadListener<ExcelReadImageUtil.DemoExcelInput>(dataList -> {
                log.info("读取到一批数据{}", JSONUtil.toJsonStr(dataList));
                demoExcelDataList.addAll(dataList);
            }, Integer.MAX_VALUE)).sheet().doRead();

            // Ⅲ. 使用ExcelReadImageUtil工具类解析Excel并提取图片——核心性能监控点
            long extractStartTime = System.currentTimeMillis();
            ExcelReadImageUtil.readImage(file.getInputStream(), demoExcelDataList, outputDir);
            long extractTime = System.currentTimeMillis() - extractStartTime;
            log.info("Excel图片提取耗时: {} ms", extractTime);

            // Ⅳ. 扫描输出目录获取已提取的图片文件列表——文件I/O性能监控点
            long scanStartTime = System.currentTimeMillis();
            File imageDir = new File(outputDir);
            File[] imageFiles = imageDir.listFiles((dir1, name) -> {
                String lowerName = name.toLowerCase();
                return lowerName.endsWith(".jpg") ||
                        lowerName.endsWith(".jpeg") ||
                        lowerName.endsWith(".png") ||
                        lowerName.endsWith(".gif");
            });

            // 构建图片文件路径列表
            List<String> extractedImages = new ArrayList<>();
            if (imageFiles != null) {
                for (File imageFile : imageFiles) {
                    // 返回相对路径用于前端Web访问
                    String relativePath = "output/readImage/" + uuid + "/" + imageFile.getName();
                    extractedImages.add(relativePath);
                    if (log.isDebugEnabled()) {
                        log.debug("发现提取的图片文件: {}, 大小: {} bytes", imageFile.getName(), imageFile.length());
                    }
                }
            }
            long buildTime = System.currentTimeMillis() - scanStartTime;
            if (log.isDebugEnabled()) {
                log.debug("文件扫描和路径构建耗时: {} ms", buildTime);
            }

            // Ⅴ. 构建响应数据并返回提取结果
            Map<String, Object> result = new HashMap<>();
            // 提取的图片总数
            result.put("extractedCount", extractedImages.size());
            // 图片文件相对路径列表
            result.put("images", extractedImages);
            // 输出目录路径
            result.put("outputDir", outputDir);

            // 总耗时统计
            long totalTime = System.currentTimeMillis() - startTime;

            // 添加性能统计信息到响应中，方便前端分析
            Map<String, Object> performanceStats = new HashMap<>();
            performanceStats.put("totalTimeMs", totalTime);
            performanceStats.put("extractTimeMs", extractTime);
            performanceStats.put("buildTimeMs", buildTime);
            performanceStats.put("fileSizeBytes", fileSize);
            performanceStats.put("imageCount", extractedImages.size());
            performanceStats.put("avgTimePerImage", !extractedImages.isEmpty() ? (double) extractTime / extractedImages.size() : 0.0);
            result.put("performanceStats", performanceStats);

            log.info("从 Excel 文件中成功提取 {} 张图片，总耗时: {} ms [提取:{} ms, 构建:{} ms, 平均:{} ms/图]",
                    extractedImages.size(), totalTime, extractTime, buildTime,
                    !extractedImages.isEmpty() ? (double) extractTime / extractedImages.size() : 0.0);
            return R.success(result);
        } catch (Exception e) {
            log.error("从Excel提取图片失败", e);
            return R.failed("从Excel提取图片失败：" + e.getMessage());
        }
    }
    /**
     * 使用EasyExcel结合图片提取功能的Excel数据导入接口（使用@ExcelProperty注解）
     *
     * <p>此接口使用改造后的ExcelReadImageUtil工具类，结合EasyExcel的@ExcelProperty注解
     * 和自定义的@ExcelImageProperty注解，实现Excel数据读取的同时自动提取图片并将文件路径
     * 回显到对应字段中。</p>
     *
     * <p>处理流程：</p>
     * <ol>
     *   <li>Ⅰ. 验证上传文件的有效性</li>
     *   <li>Ⅱ. 创建图片输出目录</li>
     *   <li>Ⅲ. 使用ExcelReadImageUtil.readImageWithAnnotation读取数据和图片</li>
     *   <li>Ⅳ. 返回读取的数据和性能统计信息</li>
     * </ol>
     *
     * <p>技术特性：</p>
     * <ul>
     *   <li>支持@ExcelProperty注解进行列映射，避免通过下标获取数据</li>
     *   <li>支持@ExcelImageProperty注解标识图片字段位置</li>
     *   <li>自动提取图片并将相对路径回显到字段中</li>
     *   <li>提供详细的性能统计信息</li>
     * </ul>
     *
     * @param file 包含数据和图片的Excel文件，支持.xlsx格式
     * @return 包含数据列表、图片信息和性能统计的响应对象
     * @since 1.0
     * @see cn.song.util.ExcelReadImageUtil#readImageWithAnnotation(InputStream, Class, String)
     * @see cn.song.util.ExcelReadImageUtil.ExcelImageImportModel Excel数据模型
     */
    @PostMapping("/extract/excel/easyexcel")
    public R<Map<String, Object>> extractExcelWithEasyExcel(@RequestParam("file") MultipartFile file) {
        // 添加性能监控
        long startTime = System.currentTimeMillis();

        try {
            // Ⅰ. 验证上传文件的有效性
            if (file.isEmpty()) {
                return R.failed("请选择Excel文件");
            }

            long fileSize = file.getSize();
            String fileName = file.getOriginalFilename();
            log.info("开始使用EasyExcel处理Excel文件: 文件={}, 大小={} bytes", fileName, fileSize);

            String uuid = UUID.randomUUID().toString();

            // Ⅱ. 创建图片输出目录
            String outputDir = "output" + File.separator + "readImage" + File.separator + uuid + File.separator;
            File dir = new File(outputDir);
            if (!dir.exists()) {
                boolean created = dir.mkdirs();
                if (!created) {
                    log.error("无法创建输出目录: {}", outputDir);
                    return R.failed("无法创建输出目录");
                }
            }

            // Ⅲ. 使用改造后的ExcelReadImageUtil读取数据和图片
            List<ExcelReadImageUtil.ExcelImageImportModel> dataList;
            try (InputStream inputStream = new ByteArrayInputStream(file.getBytes())) {
                dataList = ExcelReadImageUtil.readImageWithAnnotation(inputStream, ExcelReadImageUtil.ExcelImageImportModel.class, outputDir);
            }

            // Ⅳ. 计算图片数量（统计所有非空的图片路径字段）
            int imageCount = 0;
            for (ExcelReadImageUtil.ExcelImageImportModel data : dataList) {
                if (data.getImagePath1() != null && !data.getImagePath1().isEmpty()) {
                    imageCount++;
                }
                if (data.getImagePath2() != null && !data.getImagePath2().isEmpty()) {
                    imageCount++;
                }
            }

            // Ⅴ. 构建响应数据
            Map<String, Object> result = new HashMap<>();
            result.put("dataCount", dataList.size());
            result.put("dataList", dataList);
            result.put("imageCount", imageCount);
            result.put("outputDir", outputDir);

            // 总耗时统计
            long totalTime = System.currentTimeMillis() - startTime;

            // 性能统计信息
            Map<String, Object> performanceStats = new HashMap<>();
            performanceStats.put("totalTimeMs", totalTime);
            performanceStats.put("fileSizeBytes", fileSize);
            performanceStats.put("dataCount", dataList.size());
            performanceStats.put("imageCount", imageCount);
            result.put("performanceStats", performanceStats);

            log.info("EasyExcel处理完成，读取 {} 条数据，提取 {} 张图片，总耗时: {} ms",
                    dataList.size(), imageCount, totalTime);

            return R.success(result);

        } catch (Exception e) {
            log.error("使用EasyExcel处理Excel文件失败", e);
            return R.failed("处理Excel文件失败：" + e.getMessage());
        }
    }


    /**
     * 图片压缩处理接口
     *
     * <p>此方法接收上传的图片文件，使用自定义的图片压缩算法将其压缩到指定的目标大小。
     * 压缩过程会保持图片的原始格式和基本质量，适用于需要控制文件大小的场景。</p>
     *
     * <p>压缩流程：</p>
     * <ol>
     *   <li>Ⅰ. 验证上传文件的有效性和格式</li>
     *   <li>Ⅱ. 创建临时目录并保存原始文件</li>
     *   <li>Ⅲ. 调用FilePath工具类执行图片压缩</li>
     *   <li>Ⅳ. 计算压缩比例和文件大小变化</li>
     *   <li>Ⅴ. 返回压缩结果统计信息</li>
     * </ol>
     *
     * <p>支持的图片格式：JPG、JPEG、PNG、GIF、BMP</p>
     *
     * @param file 待压缩的图片文件，支持常见的图片格式
     * @param targetSizeKb 目标文件大小，单位为KB，默认值为100KB
     * @return 包含压缩结果的响应对象，包括原始大小、压缩后大小、压缩比例、文件路径等信息
     * @since 1.0
     * @see cn.song.util.FilePath#compressImageFile(String, String)
     */
    @PostMapping("/compress")
    public R<Map<String, Object>> compressImage(@RequestParam("file") MultipartFile file,
                                                @RequestParam(defaultValue = "100") long targetSizeKb) {
        try {
            // Ⅰ. 验证上传文件的有效性和格式
            if (file.isEmpty()) {
                return R.failed("请选择要压缩的图片文件");
            }

            // 解析原始文件信息
            String originalFileName = file.getOriginalFilename();
            String fileExtension = getFileExtension(originalFileName);
            long originalSizeKB = file.getSize() / 1024;
            log.info("开始压缩图片: {}, 原始大小: {} KB, 目标大小: {} KB", originalFileName, originalSizeKB, targetSizeKb);

            // Ⅱ. 创建临时目录并生成唯一文件名
            String tempPath = getTempDir() + File.separator + "excel-demo" + File.separator + "upload" + File.separator;
            String tempFileName = System.currentTimeMillis() + "." + fileExtension;
            String tempFilePath = tempPath + tempFileName;

            // 确保临时目录存在
            File tempDir = new File(tempPath);
            if (!tempDir.exists()) {
                // 递归创建目录结构
                boolean created = tempDir.mkdirs();
                if (!created) {
                    log.error("无法创建临时目录: {}", tempPath);
                    return R.failed("无法创建临时目录");
                }
                log.debug("成功创建临时目录: {}", tempPath);
            }

            // 保存上传的原始文件到临时目录
            File tempFile = new File(tempFilePath);
            try {
                file.transferTo(tempFile);
                log.debug("原始文件已保存到: {}", tempFilePath);
            } catch (IOException e) {
                log.error("文件保存失败: {}", tempFilePath, e);
                return R.failed("文件保存失败: " + e.getMessage());
            }

            // Ⅲ. 调用FilePath工具类执行图片压缩算法
            FilePath.compressImageFile(tempPath, tempFileName);
            log.debug("图片压缩处理完成，检查压缩结果");

            // Ⅳ. 获取压缩后的文件信息并计算压缩效果
            File compressedFile = new File(tempFilePath);
            if (!compressedFile.exists()) {
                log.error("压缩后文件不存在: {}", tempFilePath);
                return R.failed("图片压缩失败，压缩后文件不存在");
            }
            // 转换为KB
            long compressedSize = compressedFile.length() / 1024;
            double compressionRatio = originalSizeKB > 0 ? (double) compressedSize / originalSizeKB : 1.0;

            // Ⅴ. 构建并返回压缩结果统计信息
            Map<String, Object> result = new HashMap<>();
            // 原始文件大小(KB)
            result.put("originalSize", originalSizeKB);
            // 压缩后文件大小(KB)
            result.put("compressedSize", compressedSize);
            // 压缩比例(压缩后/原始)
            result.put("compressionRatio", compressionRatio);
            // 压缩后文件路径
            result.put("filePath", tempFilePath);
            // 原始文件名
            result.put("originalFileName", originalFileName);
            // 节省的存储空间(KB)
            result.put("savedSize", originalSizeKB - compressedSize);

            log.info("图片压缩成功: {} -> {} KB, 压缩比: {}%, 节省空间: {} KB",
                    originalSizeKB, compressedSize, compressionRatio, originalSizeKB - compressedSize);
            return R.success(result);
        } catch (Exception e) {
            log.error("图片压缩失败", e);
            return R.failed("图片压缩失败：" + e.getMessage());
        }
    }


    /**
     * 图片文件下载接口
     *
     * <p>此方法提供直接下载指定路径图片文件的功能，支持多种图片格式。
     * 下载过程会自动设置正确的MIME类型和文件名，保证浏览器能够正确处理下载。</p>
     *
     * <p>下载流程：</p>
     * <ol>
     *   <li>Ⅰ. 验证目标文件是否存在</li>
     *   <li>Ⅱ. 根据文件扩展名设置正确的MIME类型</li>
     *   <li>Ⅲ. 设置下载响应头信息</li>
     *   <li>Ⅳ. 流式读取和输出文件内容</li>
     * </ol>
     *
     * @param filePath 图片文件的完整路径（绝对路径或相对路径）
     * @param response HTTP响应对象，用于返回文件内容
     * @since 1.0
     * @apiNote 如果文件不存在，将返回404状态码；如果发生其他错误，返回500状态码
     */
    @GetMapping("/download")
    public void downloadImage(@RequestParam String filePath, HttpServletResponse response) {
        try {
            // Ⅰ. 验证目标文件是否存在及其有效性
            File file = new File(filePath);
            if (!file.exists()) {
                log.warn("请求下载的文件不存在: {}", filePath);
                response.setStatus(404);
                return;
            }

            if (!file.isFile() || !file.canRead()) {
                log.warn("文件不可读取或不是正常文件: {}", filePath);
                response.setStatus(403);
                return;
            }

            // 获取文件名和扩展名
            String fileName = file.getName();
            log.debug("开始下载文件: {}, 大小: {} bytes", fileName, file.length());

            // Ⅱ. 根据文件扩展名设置正确的MIME类型
            String contentType = getContentType(fileName);

            // Ⅲ. 设置下载响应头信息
            response.setContentType(contentType);
            response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
            // 设置文件大小便于进度显示
            response.setContentLength((int) file.length());

            // Ⅳ. 使用缓冲区流式读取和输出文件内容
            try (InputStream inputStream = Files.newInputStream(file.toPath())) {
                // 8KB缓冲区，提高读写效率
                byte[] buffer = new byte[8192];
                int bytesRead;
                long totalBytesWritten = 0;

                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    response.getOutputStream().write(buffer, 0, bytesRead);
                    totalBytesWritten += bytesRead;
                }

                response.getOutputStream().flush(); // 确保数据完整输出
                log.debug("文件下载完成: {}, 已传输: {} bytes", fileName, totalBytesWritten);
            }
        } catch (Exception e) {
            log.error("下载图片失败: {}", filePath, e);
            response.setStatus(500);
            // 尝试向响应体写入错误信息（如果响应头尚未发送）
            try {
                if (!response.isCommitted()) {
                    response.setContentType("text/plain; charset=utf-8");
                    response.getWriter().write("下载图片失败: " + e.getMessage());
                }
            } catch (IOException ioException) {
                log.error("写入错误响应失败", ioException);
            }
        }
    }

    /**
     * 获取图片文件详细信息
     *
     * <p>此方法用于获取指定路径图片文件的详细元数据信息，包括文件名、大小、
     * 修改时间等。这些信息对于文件管理和用户界面显示非常有用。</p>
     *
     * <p>信息获取流程：</p>
     * <ol>
     *   <li>Ⅰ. 验证文件存在性和可访问性</li>
     *   <li>Ⅱ. 获取文件系统属性信息</li>
     *   <li>Ⅲ. 计算不同单位的文件大小</li>
     *   <li>Ⅳ. 构建并返回综合信息对象</li>
     * </ol>
     *
     * @param filePath 图片文件的完整路径（支持绝对路径和相对路径）
     * @return 包含图片文件信息的响应对象，包括文件名、各种单位的文件大小、修改时间、绝对路径等
     * @throws SecurityException 当没有访问文件的权限时抛出
     * @since 1.0
     * @apiNote 返回的文件大小包括bytes、KB、MB三种单位，方便不同场景使用
     */
    @GetMapping("/info")
    public R<Map<String, Object>> getImageInfo(@RequestParam String filePath) {
        try {
            // Ⅰ. 验证文件存在性和可访问性
            File file = new File(filePath);
            if (!file.exists()) {
                log.warn("请求查询信息的文件不存在: {}", filePath);
                return R.failed("图片文件不存在");
            }

            if (!file.isFile()) {
                log.warn("指定路径不是一个文件: {}", filePath);
                return R.failed("指定路径不是一个有效文件");
            }

            // Ⅱ. 获取文件系统属性信息
            String fileName = file.getName();
            long fileSizeBytes = file.length();
            long lastModifiedTime = file.lastModified();
            String absolutePath = file.getAbsolutePath();

            // Ⅲ. 计算不同单位的文件大小以提供更好的用户体验
            double fileSizeKB = fileSizeBytes / 1024.0;
            double fileSizeMB = fileSizeKB / 1024.0;

            // 获取文件扩展名和类型
            String fileExtension = getFileExtension(fileName);
            String contentType = getContentType(fileName);

            log.debug("获取文件信息: {}, 大小: {} bytes ({} KB, {} MB)",
                    fileName, fileSizeBytes, fileSizeKB, fileSizeMB);

            // Ⅳ. 构建并返回综合信息对象
            Map<String, Object> imageInfo = new HashMap<>();
            // 文件名
            imageInfo.put("fileName", fileName);
            // 文件大小(bytes)
            imageInfo.put("fileSize", fileSizeBytes);
            // 文件大小(KB)，四舍五入
            imageInfo.put("fileSizeKB", Math.round(fileSizeKB));
            // 文件大小(MB)，保癕2位小数
            imageInfo.put("fileSizeMB", Math.round(fileSizeMB * 100.0) / 100.0);
            // 最后修改时间(时间戳)
            imageInfo.put("lastModified", lastModifiedTime);
            // 绝对路径
            imageInfo.put("absolutePath", absolutePath);
            // 文件扩展名
            imageInfo.put("fileExtension", fileExtension);
            // MIME类型
            imageInfo.put("contentType", contentType);
            // 是否可读
            imageInfo.put("readable", file.canRead());
            // 是否可写
            imageInfo.put("writable", file.canWrite());

            return R.success(imageInfo);
        } catch (Exception e) {
            log.error("获取图片信息失败", e);
            return R.failed("获取图片信息失败：" + e.getMessage());
        }
    }

    /**
     * Web端图片访问接口
     *
     * <p>此方法为前端HTML页面提供图片资源的直接访问能力，支持在Web浏览器中直接显示
     * 从Excel文件中提取的图片。接口实现了高效的文件流传输和完善的安全检查机制。</p>
     *
     * <p>技术特性：</p>
     * <ul>
     *   <li>支持多种图片格式：JPG、JPEG、PNG、GIF、BMP</li>
     *   <li>实现防目录遍历攻击的安全机制</li>
     *   <li>使用 8KB 缓冲区提高读写效率</li>
     *   <li>设置 1 小时的浏览器缓存减少服务器负载</li>
     *   <li>自动检测图片格式并设置对应的Content-Type</li>
     * </ul>
     *
     * @param imagePath 图片文件相对路径（包含UUID子目录），如：output/readImage/uuid/image.jpg
     * @param response HTTP响应对象，用于返回图片数据流
     * @since 1.0
     * @apiNote 此接口主要用于Web前端&lt;img&gt;标签的src属性，支持直接在浏览器中显示图片
     * @see #extractImagesFromExcel(MultipartFile) 图片提取方法，生成可通过此接口访问的图片文件
     */
    @GetMapping("/view")
    public void viewExtractedImage(@RequestParam @NotBlank(message = "图片文件路径未填") String imagePath, HttpServletResponse response) {
        try {
            // Ⅰ. 根据传入的完整相对路径构建文件对象
            File imageFile = new File(imagePath);

            if (!imageFile.exists()) {
                log.warn("请求访问的图片文件不存在: {}", imagePath);
                response.setStatus(404);
                return;
            }

            // Ⅱ. 验证图片文件的存在性和安全性
            if (!imageFile.isFile() || !imageFile.canRead()) {
                log.warn("图片文件不可读取或不是正常文件: {}", imageFile.getAbsolutePath());
                response.setStatus(403);
                return;
            }

            // 安全检查：防止目录遍历攻击
            String canonicalPath = imageFile.getCanonicalPath();
            String expectedPrefix = new File("output" + File.separator + "readImage").getCanonicalPath();
            if (!canonicalPath.startsWith(expectedPrefix)) {
                log.warn("检测到可能的目录遍历攻击尝试: {}", imageFile.getAbsolutePath());
                response.setStatus(403);
                return;
            }

            // Ⅲ. 设置正确的响应头信息和缓存策略
            String contentType = getContentType(imagePath);
            response.setContentType(contentType);
            // 设置1小时浏览器缓存
            response.setHeader("Cache-Control", "max-age=3600");
            // 设置文件大小
            response.setHeader("Content-Length", String.valueOf(imageFile.length()));

            log.debug("开始输出图片文件: {}, 类型: {}, 大小: {} bytes",
                    imagePath, contentType, imageFile.length());

            // Ⅳ. 使用高效缓冲区流式输出图片数据
            try (InputStream inputStream = new FileInputStream(imageFile);
                 OutputStream outputStream = response.getOutputStream()) {
// 8KB缓冲区，平衡内存使用和性能
                byte[] buffer = new byte[8192];
                int bytesRead;
                long totalBytesWritten = 0;

                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                    totalBytesWritten += bytesRead;
                }

                outputStream.flush(); // 确保数据完整传输
                log.debug("图片文件输出完成: {}, 已传输: {} bytes", imageFile.getAbsolutePath(), totalBytesWritten);
            }
        } catch (Exception e) {
            log.error("访问图片失败: {}", imagePath, e);
            response.setStatus(500);
            // 尝试向响应体写入错误信息（如果响应头尚未发送）
            try {
                if (!response.isCommitted()) {
                    response.setContentType("text/plain; charset=utf-8");
                    response.getWriter().write("访问图片失败: " + e.getMessage());
                }
            } catch (IOException ioException) {
                log.error("写入错误响应失败", ioException);
            }
        }
    }

    /**
     * 安全获取系统临时目录路径
     *
     * <p>此方法用于安全地获取系统临时目录路径，并提供备用方案。
     * 适用于临时文件存储、文件上传处理等场景。</p>
     *
     * <p>获取策略：</p>
     * <ol>
     *   <li>Ⅰ. 优先使用系统属性 java.io.tmpdir</li>
     *   <li>Ⅱ. 如果系统属性为空，使用用户主目录下的temp子目录</li>
     * </ol>
     *
     * @return 临时目录的绝对路径字符串，保证返回值不为null且不为空
     * @since 1.0
     * @apiNote 此方法不会创建目录，仅返回路径字符串，实际使用时需要手动创建
     */
    private String getTempDir() {
        // Ⅰ. 优先尝试使用系统指定的临时目录
        String tempDir = System.getProperty("java.io.tmpdir");

        // Ⅱ. 如果系统属性为空或无效，使用备用方案
        if (tempDir == null || tempDir.trim().isEmpty()) {
            // 使用用户主目录下的temp子目录作为备用方案
            tempDir = System.getProperty("user.home") + File.separator + "temp";
            log.warn("系统临时目录属性为空，使用备用目录: {}", tempDir);
        } else {
            log.debug("使用系统临时目录: {}", tempDir);
        }

        return tempDir;
    }

    /**
     * 提取文件扩展名
     *
     * <p>从完整的文件名中提取文件扩展名，用于判断文件类型和设置MIME类型。
     * 如果文件名中没有扩展名，将返回默认的"jpg"扩展名。</p>
     *
     * <p>处理逻辑：</p>
     * <ol>
     *   <li>Ⅰ. 检查文件名的有效性（非空且包含点号）</li>
     *   <li>Ⅱ. 从最后一个点号后面提取扩展名</li>
     *   <li>Ⅲ. 如果没有找到扩展名，返回默认值</li>
     * </ol>
     *
     * @param fileName 完整的文件名（包括扩展名），可以为null
     * @return 文件扩展名（不包含点号），如果未找到则返回"jpg"
     * @since 1.0
     * @apiNote 返回的扩展名不包含点号前缀，例如："test.jpg" -> "jpg"
     * @see #getContentType(String) 使用此方法的返回值判断文件MIME类型
     */
    private String getFileExtension(String fileName) {
        // Ⅰ. 检查文件名的有效性和格式
        if (fileName == null || fileName.trim().isEmpty()) {
            log.debug("文件名为空，返回默认扩展名: jpg");
            return "jpg"; // 默认扩展名
        }

        // Ⅱ. 查找最后一个点号的位置
        int lastDotIndex = fileName.lastIndexOf(".");

        // Ⅲ. 根据点号位置提取扩展名或返回默认值
        if (lastDotIndex == -1 || lastDotIndex == fileName.length() - 1) {
            // 没有找到点号或点号在文件名末尾（无扩展名）
            log.debug("文件名 '{}' 中未找到有效扩展名，返回默认扩展名: jpg", fileName);
            return "jpg";
        }

        // 提取扩展名（不包含点号）
        String extension = fileName.substring(lastDotIndex + 1);
        log.debug("从文件名 '{}' 中提取到扩展名: {}", fileName, extension);
        return extension;
    }

    /**
     * 下载图片Excel模板（导出到本地output目录）
     *
     * @return 导出结果
     */
    @PostMapping("/template/download/local")
    public R<Map<String, Object>> downloadImageTemplateToLocal() {
        try {
            // 创建output目录
            String outputDir = "output" + File.separator + "imageTemplate" + File.separator;
            File dir = new File(outputDir);
            if (!dir.exists()) {
                boolean created = dir.mkdirs();
                if (!created) {
                    log.error("无法创建输出目录: {}", outputDir);
                    return R.failed("无法创建输出目录");
                }
            }

            // 生成文件名
            String fileName = "image_template_" + System.currentTimeMillis() + ".xlsx";
            String filePath = outputDir + fileName;

            // 创建模板数据
            List<ExcelReadImageUtil.DemoExcelInput> data = createTemplateData();

            // 导出Excel
            EasyExcel.write(filePath, ExcelReadImageUtil.DemoExcelInput.class)
                    .sheet("图片模板")
                    .doWrite(data);

            Map<String, Object> result = new HashMap<>();
            result.put("filePath", filePath);
            result.put("fileName", fileName);
            result.put("dataCount", data.size());
            result.put("outputDir", outputDir);

            log.info("图片模板已导出到: {}", filePath);
            return R.success(result);
        } catch (Exception e) {
            log.error("导出图片模板到本地失败", e);
            return R.failed("导出图片模板失败：" + e.getMessage());
        }
    }

    /**
     * 下载EasyExcel图片导入专用模板（输出流）
     *
     * <p>此接口专门为EasyExcel图片导入功能生成对应的Excel模板文件。
     * 模板结构与ExcelImageImportModel完全一致，包含姓名、年龄、图片列和备注字段。</p>
     *
     * <p>模板特点：</p>
     * <ul>
     *   <li>字段结构与ExcelImageImportModel完全匹配</li>
     *   <li>包含示例数据和图片</li>
     *   <li>支持@ExcelProperty和@ExcelImageProperty注解处理</li>
     *   <li>提供标准的Excel格式下载</li>
     * </ul>
     *
     * @param response HTTP响应对象
     * @since 1.0
     * @see cn.song.util.ExcelReadImageUtil.ExcelImageImportModel 对应的数据模型
     */
    @GetMapping("/template/download/easyexcel")
    public void downloadEasyExcelImportTemplate(HttpServletResponse response) {
        try {
            // Ⅰ. 设置响应头
            String fileName = "easyexcel_import_template_" + System.currentTimeMillis() + ".xlsx";
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);

            // Ⅱ. 创建模板数据
            List<ExcelReadImageUtil.ExcelImageImportModel> templateData = createEasyExcelTemplateData();

            // Ⅲ. 导出Excel到输出流
            EasyExcel.write(response.getOutputStream(), ExcelReadImageUtil.ExcelImageImportModel.class)
                    .sheet("EasyExcel导入模板")
                    .doWrite(templateData);

            log.info("EasyExcel导入模板已通过输出流导出，文件名: {}", fileName);
        } catch (Exception e) {
            log.error("导出EasyExcel导入模板失败", e);
            response.setStatus(500);
            try {
                response.getWriter().println("导出失败: " + e.getMessage());
            } catch (IOException ioException) {
                log.error("写入错误响应失败", ioException);
            }
        }
    }

    /**
     * 创建EasyExcel导入模板数据
     *
     * <p>生成与ExcelImageImportModel结构完全一致的模板数据，
     * 包含示例数据和图片，便于用户了解正确的数据格式。</p>
     *
     * @return 模板数据列表
     */
    private List<ExcelReadImageUtil.ExcelImageImportModel> createEasyExcelTemplateData() {
        List<ExcelReadImageUtil.ExcelImageImportModel> data = new ArrayList<>();

        try {
            // Ⅰ. 检查示例图片文件
            String imagePath = "input" + File.separator + "readImage" + File.separator + "image.png";
            File imageFile = new File(imagePath);

            // Ⅱ. 创建示例数据
            for (int i = 1; i <= 3; i++) {
                ExcelReadImageUtil.ExcelImageImportModel item = new ExcelReadImageUtil.ExcelImageImportModel();
                item.setName("用户" + i);
                item.setAge(20 + i);
                item.setRemark("这是第" + i + "条示例数据");

                // 如果示例图片存在，设置图片字段（用于显示）
                if (imageFile.exists()) {
                    item.setImagePath1("请在此列插入图片" + i + "-1(wps-插入-图片-浮动式图片)");
                    item.setImagePath2("请在此列插入图片" + i + "-2(wps-插入-图片-浮动式图片)");
                } else {
                    item.setImagePath1("图片文件不存在，请准备图片文件(wps-插入-图片-浮动式图片)");
                    item.setImagePath2("图片文件不存在，请准备图片文件(wps-插入-图片-浮动式图片)");
                }

                data.add(item);
            }

            log.info("成功创建EasyExcel导入模板数据，包含 {} 条示例记录", data.size());
        } catch (Exception e) {
            log.error("创建EasyExcel导入模板数据失败", e);
            // 创建基本的示例数据
            ExcelReadImageUtil.ExcelImageImportModel basicItem = new ExcelReadImageUtil.ExcelImageImportModel();
            basicItem.setName("示例用户");
            basicItem.setAge(25);
            basicItem.setImagePath1("请在此列插入图片1(wps-插入-图片-浮动式图片)");
            basicItem.setImagePath2("请在此列插入图片2(wps-插入-图片-浮动式图片)");
            basicItem.setRemark("这是示例数据");
            data.add(basicItem);
        }

        return data;
    }

    /**
     * 创建图片模板数据内容
     *
     * <p>此方法负责生成包含图片数据的Excel模板内容，从指定路径读取图片文件并转换为多种格式。
     * 支持图片不存在时的容错处理，确保始终返回有效的模板数据。</p>
     *
     * <p>数据创建流程：</p>
     * <ol>
     *   <li>Ⅰ. 检查指定路径的图片文件是否存在</li>
     *   <li>Ⅱ. 读取图片数据并转换为字节数组、URL、输入流等多种格式</li>
     *   <li>Ⅲ. 创建DemoExcelInput对象并填充图片数据</li>
     *   <li>Ⅳ. 返回包含完整图片信息的模板数据列表</li>
     * </ol>
     *
     * @return 包含图片数据的模板对象列表，用于Excel生成
     * @since 1.0
     * @apiNote 如果图片文件不存在，会创建包含错误信息的默认数据
     */
    private List<ExcelReadImageUtil.DemoExcelInput> createTemplateData() {
        List<ExcelReadImageUtil.DemoExcelInput> data = new ArrayList<>();

        try {
            // Ⅰ. 检查指定路径的图片文件是否存在
            String imagePath = "input" + File.separator + "readImage" + File.separator + "image.png";
            File imageFile = new File(imagePath);

            if (!imageFile.exists()) {
                log.warn("模板图片文件不存在: {}, 创建默认空数据", imagePath);
                // 创建包含提示信息的空数据示例
                ExcelReadImageUtil.DemoExcelInput emptyData = new ExcelReadImageUtil.DemoExcelInput();
                emptyData.setName("热卖商品");
                emptyData.setImageStr("图片文件不存在: " + imagePath);
                data.add(emptyData);
                return data;
            }

            // Ⅱ. 读取图片数据并转换为多种格式以适应不同的使用场景
            // 字节数组格式
            byte[] imageBytes = Files.readAllBytes(Paths.get(imagePath));
            // URL格式
            URL imageUrl = imageFile.toURI().toURL();
            // 输入流格式
            InputStream imageInputStream = Files.newInputStream(imageFile.toPath());

            // Ⅲ. 创建并填充Excel模板数据对象
            ExcelReadImageUtil.DemoExcelInput templateData = new ExcelReadImageUtil.DemoExcelInput();
            // 产品名称
            templateData.setName("热卖商品");
            // 图片URL字符串（用于EasyExcel图片转换器）
            templateData.setImageStr(imageUrl.toString());
            // 图片URL对象
            templateData.setImageUrl(imageUrl);
            // 图片输入流
            templateData.setInputStream(imageInputStream);
            // 图片字节数组
            templateData.setBytes(imageBytes);

            data.add(templateData);

            log.info("成功创建图片模板数据，图片路径: {}, 大小: {} bytes", imagePath, imageBytes.length);
        } catch (Exception e) {
            log.error("创建图片模板数据时发生异常", e);
            // 创建包含错误信息的默认数据
            ExcelReadImageUtil.DemoExcelInput errorData = new ExcelReadImageUtil.DemoExcelInput();
            errorData.setName("热卖商品");
            errorData.setImageStr("创建模板数据失败: " + e.getMessage());
            data.add(errorData);
        }

        return data;
    }

    /**
     * 根据文件扩展名获取对应的MIME内容类型
     *
     * <p>此方法根据文件的扩展名返回相应的MIME类型，用于设置HTTP响应的Content-Type头。
     * 支持常见的图片格式，对于不支持的格式返回通用的二进制流类型。</p>
     *
     * <p>支持的图片格式：</p>
     * <ul>
     *   <li>JPG/JPEG → image/jpeg</li>
     *   <li>PNG → image/png</li>
     *   <li>GIF → image/gif</li>
     *   <li>BMP → image/bmp</li>
     *   <li>其他 → application/octet-stream</li>
     * </ul>
     *
     * @param fileName 文件名（包含扩展名）
     * @return 对应的MIME类型字符串
     * @since 1.0
     * @see #getFileExtension(String) 获取文件扩展名的方法
     */
    private String getContentType(String fileName) {
        // 获取文件扩展名并转换为小写进行匹配
        String extension = getFileExtension(fileName).toLowerCase();

        // 根据扩展名返回相应的MIME类型
        switch (extension) {
            case "jpg":
            case "jpeg":
                // JPEG图片格式
                return "image/jpeg";
            case "png":
                // PNG图片格式
                return "image/png";
            case "gif":
                // GIF动图格式
                return "image/gif";
            case "bmp":
                // BMP位图格式
                return "image/bmp";
            default:
                log.debug("未识别的文件扩展名: {}, 使用默认MIME类型", extension);
                // 通用二进制流类型
                return "application/octet-stream";
        }
    }
}
